package com.caishi.lkx.user.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.util.StrUtil;
import com.caishi.lkx.common.config.DiamondConfig;
import com.caishi.lkx.common.context.TokenData;
import com.caishi.lkx.common.ienum.DeviceType;
import com.caishi.lkx.user.ienum.type.HaveType;
import com.caishi.lkx.user.ienum.type.UserAccountType;
import com.caishi.lkx.user.ienum.type.UserChannelType;
import com.caishi.lkx.user.mapper.LoginExpandMapper;
import com.caishi.lkx.user.mapper.LoginInfoMapper;
import com.caishi.lkx.user.model.LoginExpandModel;
import com.caishi.lkx.user.model.LoginInfoModel;
import com.caishi.lkx.user.model.MemberModel;
import com.caishi.lkx.user.model.UserModel;
import com.caishi.lkx.user.service.ILoginInfoService;
import com.caishi.lkx.user.service.IMemberService;
import com.caishi.lkx.user.service.IUserService;
import com.zzw.common.IUserContext;
import com.zzw.common.Wrappers;
import com.zzw.common.entity.IBaseMapper;
import com.zzw.common.entity.impl.BaseEntity;
import com.zzw.common.exception.BizException;
import com.zzw.common.exception.BizRuntimeException;
import com.zzw.common.lock.RedissonLock;
import com.zzw.common.mysql.handler.DataEncryptionHandler;
import com.zzw.common.schedule.KSchedule;
import com.zzw.common.schedule.KScheduleDelay;

import com.caishi.lkx.user.UserResultCode;

import com.caishi.lkx.user.login.LoginService;

import com.zzw.common.service.service.impl.BaseServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.Lock;

/**
 * @author by keray
 * date:2019/8/1 15:19
 */
@Service("loginInfoServiceImpl")
@Slf4j
//@DubboService(interfaceClass = ILoginInfoBaseService.class)
public class LoginInfoServiceImpl extends BaseServiceImpl<LoginInfoModel> implements ILoginInfoService {

    @Resource
    private LoginInfoMapper loginInfoMapper;

    @Resource
    private LoginExpandMapper loginExpandMapper;

    @Resource
    private IUserContext<TokenData> userContext;

    @Resource
    private DiamondConfig diamondConfig;

    @Resource
    private IUserService<UserModel> userService;

    @Resource
    private IMemberService memberService;


    @Override
    public IBaseMapper<LoginInfoModel, String> getMapper() {
        return loginInfoMapper;
    }

    @SneakyThrows
    @Override
    public LoginInfoModel insert(LoginInfoModel entity) {
        Lock lock = RedissonLock.lock("addLogin:" + entity.getAccount());
        TransactionSynchronizationManager.registerSynchronization(
                new TransactionSynchronization() {
                    @Override
                    public void afterCompletion(int status) {
                        if (!Thread.currentThread().isInterrupted()) {
                            lock.unlock();
                            return;
                        }
                        throw new BizRuntimeException();
                    }
                }
        );
        if (loginInfoMapper.selectCount(
                wrappers()
                        .eq(LoginInfoModel::getLoginType, entity.getLoginType())
                        .eq(LoginInfoModel::getAccount, entity.getAccount())
        ) > 0) {
            throw new BizRuntimeException(UserResultCode.loginInfoExits);
        }
        return super.insert(entity);
    }

    @Override
    public Boolean delete(List<String> strings) {
        loginExpandMapper.deleteBatchIds(strings);
        return super.delete(strings);
    }

    @Override
    public Boolean delete(String s) {
        loginExpandMapper.deleteById(s);
        return super.delete(s);
    }

    @Override
    public LoginInfoModel selectLoginInfoUserAccount(String userAccount, UserAccountType type) {
        return loginInfoMapper.selectOne(
                wrappers()
                        .eq(LoginInfoModel::getAccount, userAccount)
                        .eq(LoginInfoModel::getLoginType, type)
        );
    }

    public static void main(String[] args) {
        System.out.println(DataEncryptionHandler.encrypt("admin"));
        System.out.println(DataEncryptionHandler.decrypt("04bba87803d5e1271e6b235e34c5dde01c94629e3f3d25e7102a665e954eb45e827f4c8f141c49211f381c5eb1ea1a"));
    }
    /**
     * oldPassword为空时表示重置密码
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateUserUsernameAndPasswordLoginInfo(String userId, String password, String oldPassword) throws BizException {
        MemberModel model = memberService.getById(userId);
        if (null != model) {
            if (UserChannelType.carsi.equals(model.getChannelType())) {
                throw new BizRuntimeException(UserResultCode.casiLoginError);
            }
        }
        List<LoginInfoModel> loginInfoModels = selectList(
                Wrappers.<LoginInfoModel>lambdaQuery()
                        .eq(LoginInfoModel::getUserId, userId)
                        .in(LoginInfoModel::getLoginType, Arrays.asList(UserAccountType.username, UserAccountType.group))
        );
        if (StrUtil.isNotBlank(oldPassword)) {
            if (!loginInfoModels.isEmpty()) {
                loginInfoModels = loginInfoModels.stream()
                        .filter(v -> Objects.equals(LoginService.passwordEncryption(oldPassword), v.getCertificate()))
                        .toList();
                if (CollUtil.isEmpty(loginInfoModels)) {
                    throw new BizRuntimeException("原密码错误");
                }
            }
        }
        if (loginInfoModels.stream().noneMatch(v -> v.getLoginType() == UserAccountType.username)) {
            // 直接新增账号密码登录
            var userModel = userService.getById(userId);
            userAddNewLogin(userModel.getMobile(), LoginService.passwordEncryption(password), UserAccountType.username, userId);
        }
        if (loginInfoModels.isEmpty()) return 1;
        return loginInfoMapper.update(null, Wrappers.<LoginInfoModel>lambdaUpdate()
                .set(LoginInfoModel::getCertificate, LoginService.passwordEncryption(password))
                .in(BaseEntity::getId, loginInfoModels.stream().map(BaseEntity::getId).toList())
        );
    }


    @Override
    public LoginInfoModel selectLoginInfoByCredentialsAndAccount(String credentials, String userAccount, UserAccountType type) {
        return loginInfoMapper.selectOne(wrappers()
                .eq(LoginInfoModel::getLoginType, type)
                .eq(LoginInfoModel::getAccount, userAccount)
                .eq(LoginInfoModel::getCertificate, credentials));
    }

    @Override
    public LoginInfoModel selectFirstLoginInfoByCredentials(String credentials, UserAccountType type) {
        return loginInfoMapper.selectFirst(wrappers()
                .eq(LoginInfoModel::getLoginType, type)
                .eq(LoginInfoModel::getCertificate, credentials));
    }

    @Override
    public Boolean checkUsernameExits(String username, String oldUsername) {
        return selectCount(wrappers()
                .eq(LoginInfoModel::getAccount, username)
                .eq(LoginInfoModel::getLoginType, UserAccountType.username)
                .ne(StrUtil.isNotBlank(oldUsername), LoginInfoModel::getAccount, oldUsername)
        ) > 0;
    }

    @Override
    public String selectUsername(String userId) {
        LoginInfoModel model = loginInfoMapper.selectFirst(
                wrappers()
                        .select(LoginInfoModel::getAccount)
                        .eq(LoginInfoModel::getUserId, userId)
                        .eq(LoginInfoModel::getLoginType, UserAccountType.username)

        );
        return model == null ? null : model.getAccount();
    }

    @Override
    public List<LoginInfoModel> selectUserLoginInfo(String userId) {
        return loginInfoMapper.selectList(
                wrappers()
                        .eq(LoginInfoModel::getUserId, userId)
        );
    }


    @Override
    public LoginInfoModel selectUserLoginInfo(String userId, UserAccountType userAccountType) {
        return loginInfoMapper.selectFirst(wrappers()
                .eq(LoginInfoModel::getUserId, userId)
                .eq(LoginInfoModel::getLoginType, userAccountType));
    }

    @Override
    public void updatePassword(String userId, UserAccountType accountType, String newPass) {
        loginInfoMapper.update(null, Wrappers.<LoginInfoModel>lambdaUpdate()
                .set(LoginInfoModel::getCertificate, LoginService.passwordEncryption(newPass))
                .eq(LoginInfoModel::getUserId, userId)
                .eq(accountType != null, LoginInfoModel::getLoginType, accountType)
        );
    }

    @Override
    public void loginFail(LoginInfoModel loginInfoModel, DeviceType deviceType, String deviceName) {
        if (loginInfoModel == null) {
            return;
        }
        LoginExpandModel loginExpandModel = loginExpandMapper.selectById(loginInfoModel.getId());
        if (loginExpandModel == null) {
            loginExpandModel = LoginExpandModel.builder()
                    .loginIp(userContext.currentIp())
                    .loginTime(LocalDateTime.now())
                    .loginIp(userContext.currentIp())
                    .failureCount(0)
                    .userId(loginInfoModel.getUserId())
                    .deviceType(deviceType)
                    .deviceName(deviceName)
                    .build();
            loginExpandModel.setId(loginInfoModel.getId());
            loginExpandMapper.insert(loginExpandModel);
        }
//        if (loginExpandModel.getFailureCount() >= diamondConfig.getLoginFailLockCount()) {
//            // 失败次数大于5次
//            loginInfoMapper.update(null, Wrappers.<LoginInfoModel>lambdaUpdate()
//                    .set(LoginInfoModel::getLock, true)
//                    .eq(BaseEntity::getId, loginExpandModel.getId())
//            );
//            throw new BizRuntimeException(UserResultCode.loginFail5);
//        }
        loginExpandModel.setFailureCount(loginExpandModel.getFailureCount() + 1);
        loginExpandMapper.updateById(loginExpandModel);
        if (loginExpandModel.getFailureCount() == 1) {
            owner(LoginInfoServiceImpl.class).loginOpenLock(loginExpandModel.getId(), "s" + diamondConfig.getLoginLockSeconds());
        }
    }

    @Override
    public void loginSuccess(LoginInfoModel infoModel, DeviceType deviceType, String deviceName) {
        // 乐观处理
        if (loginExpandMapper.update(null, Wrappers.<LoginExpandModel>lambdaUpdate()
                .set(LoginExpandModel::getLoginTime, LocalDateTime.now())
                .set(LoginExpandModel::getFailureCount, 0)
                .set(LoginExpandModel::getLoginIp, userContext.currentIp())
                .set(LoginExpandModel::getDeviceType, deviceType)
                .eq(LoginExpandModel::getId, infoModel.getId())
        ) == 1) {
            return;
        }
        LoginExpandModel loginExpandModel = LoginExpandModel.builder()
                .loginIp(userContext.currentIp())
                .loginTime(LocalDateTime.now())
                .loginIp(userContext.currentIp())
                .failureCount(0)
                .userId(infoModel.getUserId())
                .deviceType(deviceType)
                .deviceName(deviceName)
                .build();
        loginExpandModel.setId(infoModel.getId());
        loginExpandMapper.insert(loginExpandModel);
    }

    @Override
    public void updateLoginInfoByUserIdAndType(LoginInfoModel loginInfoModel, String userId, @NotNull UserAccountType smsCodeType) {
        loginInfoMapper.update(loginInfoModel,
                Wrappers.<LoginInfoModel>lambdaUpdate()
                        .eq(LoginInfoModel::getLoginType, smsCodeType)
                        .eq(LoginInfoModel::getUserId, userId)
        );
    }

    @Override
    public void changeLoginInfoEnable(String userId, Boolean enable) {
        loginInfoMapper.update(null, Wrappers.<LoginInfoModel>lambdaUpdate()
                .set(LoginInfoModel::getEnable, enable)
                .eq(LoginInfoModel::getUserId, userId)
        );
    }

    @Override
    public LoginInfoModel userAddNewLogin(String userAccount, String credentials, UserAccountType type, String userId) {
        if (type == UserAccountType.group || type == UserAccountType.username) {
            UserModel userModel = userService.getById(userId);
            //第三方密码登录不做判断
            MemberModel memberModel = memberService.getUserDetail(userId, userModel);
            if (StrUtil.isNotBlank(diamondConfig.getPassRegex()) && !credentials.matches(diamondConfig.getPassRegex()) && credentials.length() != 32 && (null == memberModel || !UserChannelType.sso.equals(memberModel.getChannelType())) && Arrays.stream(userModel.getHaveTypes()).noneMatch(f -> f.equals(HaveType.ykt))) {
                throw new BizRuntimeException(UnicodeUtil.toString(diamondConfig.getPassRegexDesc()));
            }
        }
        return this.insert(LoginInfoModel.builder()
                .certificate(credentials)
                .account(userAccount)
                .loginType(type)
                .enable(true)
                .userId(userId)
                .lock(false)
                .build());
    }

    @Override
    public int getUserHaveLogin(String userId) {
        List<LoginInfoModel> loginInfoModels = selectUserLoginInfo(userId);
        int result = 0x00;
        for (LoginInfoModel infoModel : loginInfoModels) {
            result |= getUserHaveLogin(infoModel.getLoginType());
        }
        return result;
    }

    public int getUserHaveLogin(UserAccountType type) {
        return switch (type) {
            case username -> 0x01;
            case phone -> 0x02;
            case group -> 0x04;
            case weixin -> 0x08;
            case qq -> 0x10;
            case iphone -> 0x20;
            default -> 0;
        };
    }

    @KSchedule(beanName = "loginInfoServiceImpl", desc = "登录信息解锁")
    public void loginOpenLock(String loginId, @KScheduleDelay String kz) {
        loginInfoMapper.update(null, Wrappers.<LoginInfoModel>lambdaUpdate()
                .set(LoginInfoModel::getLock, false)
                .eq(BaseEntity::getId, loginId)
        );
        loginExpandMapper.update(null, Wrappers.<LoginExpandModel>lambdaUpdate()
                .set(LoginExpandModel::getFailureCount, 0)
                .eq(LoginExpandModel::getId, loginId)
        );
    }

    @Override
    public LoginExpandModel userLastLoginData(String userId) {
        return loginExpandMapper.userLastLoginData(userId);
    }

    @SneakyThrows
    @Override
    public void updateAccountByUserId(String userId, String userAccount, UserAccountType type) {
        if (StrUtil.isNotBlank(userAccount)) {
            RedissonLock.lock("update:userAccount:" + type.name() + ":" + userAccount, key -> {
                LoginInfoModel loginInfoModel = this.selectFirst(Wrappers.<LoginInfoModel>lambdaQuery().eq(LoginInfoModel::getAccount, userAccount).eq(LoginInfoModel::getLoginType, type));
                if (null == loginInfoModel) {
                    this.update(null, Wrappers.<LoginInfoModel>lambdaUpdate().eq(LoginInfoModel::getUserId, userId).eq(LoginInfoModel::getLoginType, type).set(LoginInfoModel::getAccount, userAccount));
                }
            });
        }


    }


    public static int loginTypeCode(UserAccountType oldType) {
        return switch (oldType) {
            case username -> 0x01;
            case phone -> 0x02;
            case weixin -> 0x04;
            case group -> 0x08;
            case qq -> 0x16;
            default -> 0;
        };
    }



}
